#-*- coding: utf-8 -*-
#===============================================================================
# Koro Business Environment
# Copyright (C) 2008  Antonio Corroppoli
#
#This file is part of Koro BE.
# 
# Koro BE is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# Koro BE is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with Koro BE; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#===============================================================================

'''
Created on 14/ago/2011

@author: antstud
'''

from decimal import Decimal
from koro.modules.commerciale.model import Listino
from sqlalchemy.orm import object_session,reconstructor
from koro import util
from koro.core import exception
from koro.core.model import IDObject


#===============================================================================
# CategoriaProdotto
#===============================================================================
class CategoriaProdotto(IDObject):
    def __init__(self,nome='Nuova Categoria'):
        IDObject.__init__(self)        
        self.nome=nome
        self.categoria_padre=None
        self.sottocategorie=[]
    
    @reconstructor
    def on_load(self):
        self.sottocategorie=object_session(self).query(self.__class__).\
        filter_by(categoria_padre=self.ID).all()
    
    def validate(self):
        if not bool(self.nome.lstrip()):
            raise exception.KoroInvalidObject("Il campo 'Nome' è richiesto")

    def get_nome(self):
        return self.nome

    def set_nome(self, value):
        self.nome = value
    
    def add_sottocategoria(self,nome='Nuova Sottocategoria'):
        subcateg=CategoriaProdotto(nome)
        subcateg.categoria_padre=self.ID
        self.sottocategorie.append(subcateg)
        return subcateg
    
    def remove_sottocategoria(self,subcateg):
        for item in self.sottocategorie:
            if item is subcateg:
                self.sottocategorie.remove(subcateg)
                #del subcateg
                return


#===============================================================================
# InfoFornitore
#===============================================================================
class InfoFornitore(object):
    def __init__(self,ID_prodotto,fornitore_object):
        self.fk_prodotto=ID_prodotto
        self.fk_fornitore=fornitore_object.ID
        self.fornitore=fornitore_object
        self.prezzo_fornitore=Decimal('0.00')
        self.codice_prod_forn=''
        self.principale=False
    
    @reconstructor
    def on_load(self):
        self.prezzo_fornitore=util.to_decimal(self.prezzo_fornitore)
    
    def validate(self):
        return True
    
    def set_info_fornitore(self,fornitore,cod_prod_forn,prezzo,principale):
        self.set_fornitore(fornitore)#deve essere il primo
        self.codice_prod_forn=cod_prod_forn
        self.set_principale(principale)
        self.set_prezzo_fornitore(prezzo)
    
    def set_fornitore(self,fornitore):
        if self.prodotto.has_fornitore(fornitore,self):
            raise exception.KoroDuplicatedObject('Il fornitore selezionato è già nella lista fornitori del prodotto.')
        self.fornitore=fornitore
        self.fk_fornitore=fornitore.ID
    
    def principale_fmt(self):
        if self.principale:
            return 'P'
        return ''
    
    def get_prezzo_fornitore_fmt(self):
        return util.euro(self.prezzo_fornitore)
    
    def set_prezzo_fornitore(self,prezzo_forn):
        self.prezzo_fornitore=Decimal(str(prezzo_forn))
        if self.principale:
            self.prodotto.set_prezzo_fornitore_principale(self.prezzo_fornitore)
    
    def get_ID_fornitore(self):
        return self.fornitore.ID
    
    def set_info(self,codice_prod_forn,prezzo_fornitore):
        self.codice_prod_forn=codice_prod_forn
        self.prezzo_fornitore=Decimal(str(prezzo_fornitore))
    
    def get_fornitore_fmt(self):
        return self.fornitore.ragione_sociale
    
    def set_principale(self,principale):
        if self.principale:#non posso modificare se fornitore principale
            return
        self.principale=principale


#===============================================================================
# Prodotto
#===============================================================================
class Prodotto(IDObject):
    def __init__(self):
        IDObject.__init__(self)
        self.barcode = ''
        self.descrizione=''
        self.fk_categoria=None
        self.categoria=None
        self.fk_sottocategoria=None
        self.sottocategoria=None
        self.fk_fornitore_principale=None
        self.fornitore_principale=None
        self.fk_iva=None
        self.aliquota_iva=None
        self.info_prezzi=[]#lista
        self.info_fornitori=[]#lista
        self.con_magazzino=True
        self.note=''
        self.giacenza=0
    
    def validate(self):
        if not bool(self.descrizione.lstrip()):
            raise exception.KoroInvalidObject("Il campo 'Descrizione' è richiesto")
    
    def get_giacenza(self):
        session=object_session(self)
        query=session.query("giacenza")
        statement='select sum(quantita*segno) as giacenza\
        from movimenti_magazzino \
        where fk_prodotto={0}'.format(self.ID)
        query=query.from_statement(statement)
        giacenza=query.scalar()
        if giacenza is None:
            return 0
        return giacenza    
    
    def init_prezzi(self,listini):
        '''Questo metodo inizializza i prezzi del prodotto. 
        Deve essere chiamato solo alla creazione manuale di un prodotto.'''
        self.info_prezzi=[]
        for listino in listini:
            prezzo=PrezzoProdotto(self,listino)
            self.info_prezzi.append(prezzo)
    
    def get_aliquota_iva(self):
        return self.aliquota_iva            
    
    def has_magazzino(self):
        return bool(self.con_magazzino)
    
    def add_info_fornitore(self,fornitore,cod_prod_forn='',prezzo_fornitore='0.00',principale=False):
        if self.info_fornitori is None:
            self.info_fornitori=[]
        info_fornitore=self._info_fornitore_factory(fornitore)
        if len(self.info_fornitori)==0:#se è l'unico è pricipale
            principale=True
        if principale:
            self._set_fornitore_principale(info_fornitore)
        info_fornitore.set_info(cod_prod_forn,prezzo_fornitore)
        self.info_fornitori.append(info_fornitore)
        self.aggiorna_listino_fornitore_principale()
        return info_fornitore
    
    def set_barcode(self,bc):
        self.barcode = bc
    
    def get_barcode(self):
        return self.barcode
    
    def remove_info_fornitore(self,info_fornitore):
        try:
            self.info_fornitori.remove(info_fornitore)
            if info_fornitore.principale:
                self.info_fornitori[0].principale=True
                self.set_prezzo_fornitore_principale(self.info_fornitori[0].prezzo_fornitore)
            #del info_fornitore#dovrebbe eliminarlo dal database
            #self.cestino.append(info_fornitore)
        except:
            self.aggiorna_listino_fornitore_principale()#puo' succedere se non c sono + fornitori
    
    def _info_fornitore_factory(self,fornitore):
        '''Funzione interna per creare un nuovo oggetto InfoFornitore.
        Se il fornitore è già presente nella lista fornitori, restituisce il fornitore esistente.'''
        info_fornitore=self._find_info_fornitore(fornitore)
        if info_fornitore is None:
            info_fornitore=InfoFornitore(self.ID,fornitore)
            return info_fornitore
        raise exception.KoroDuplicatedObject('Il fornitore selezionato è già nella lista fornitori del prodotto.')
    
    def _find_info_fornitore(self,fornitore):
        for item in self.info_fornitori:
            if item.fornitore.ID==fornitore.ID:
                return item
        return None
    
    def has_fornitore(self,fornitore,da_escludere=None):
        for item in self.info_fornitori:
            if item is not da_escludere and item.fornitore.ID==fornitore.ID:
                return True
        return False
    
    def set_fornitore_principale(self,fornitore):
        '''Chiamare questo metodo per impostare il fornitore principale.'''
        info_fornitore=self._find_info_fornitore(fornitore)
        if info_fornitore is None:
            return
        self._set_fornitore_principale(info_fornitore)
        #self.set_prezzo_fornitore_principale(info_fornitore.prezzo_fornitore)
    
    def _set_fornitore_principale(self,info_fornitore):
        '''Metodo interno per settare il fornitore principale.'''
        self.fk_fornitore_principale=info_fornitore.fornitore.ID
        self.fornitore_principale=info_fornitore.fornitore
        for item in self.info_fornitori:
            item.principale=False
        info_fornitore.principale=True

    def get_info_fornitori(self):
#        if not hasattr(self,'info_fornitori') or self.info_fornitori is None:
#            self._load_info_fornitori()
        return self.info_fornitori
    
    def _get_info_fornitore_principale(self):
        for item in self.get_info_fornitori():
            if item.principale:
                return item
        return None
        #raise Exception('Prodotto._get_info_fornitore_principale: Nessun fornitore principale.')
    
    def get_fornitore_principale(self):
        info=self._get_info_fornitore_principale()
        if info is not None:
            return info.fornitore
        return None        
    
    def get_fornitore_principale_fmt(self):
        f=self.get_fornitore_principale()
        if f is None:
            return ''
        return f.ragione_sociale
    
    def get_prezzo_fornitore_principale(self):
        info=self._get_info_fornitore_principale()
        if info is not None:
            return info.prezzo_fornitore
        return Decimal('0.00')
    
    def get_prezzo_fornitore_principale_fmt(self):
        return util.euro(self.get_prezzo_fornitore_principale())
    
    def get_prezzo_fornitore(self,fornitore):
        info_fornitore=self._find_info_fornitore(fornitore)
        if info_fornitore is not None:
            return info_fornitore.prezzo_fornitore
        return Decimal('0.00')
    
    def get_codice_prod_forn(self,fornitore=None):
        info_forn=None
        if fornitore is None or not fornitore.is_fornitore():
            info_forn=self._get_info_fornitore_principale()
        else:
            info_forn=self._find_info_fornitore(fornitore)
        if info_forn is not None:
            return info_forn.codice_prod_forn
        return ''
    
    def get_info_prezzo(self,codice_listino):
        for item in self.info_prezzi:
            if item.fk_listino==codice_listino:
                return item
    
    def aggiorna_listino_fornitore_principale(self):
        self.set_prezzo(Listino.PREZZO_FORNITORE, self.get_prezzo_fornitore_principale())
    
    def set_prezzo_fornitore_principale(self,prezzo):
        self.set_prezzo(Listino.PREZZO_FORNITORE, prezzo)
    
    def get_info_prezzi(self):
        '''Restituisce i prezzi del prodotto eccetto il prezzo del listino fornitore.'''
        info_prezzi=[]
        for item in self.info_prezzi:
            if item.fk_listino!=Listino.PREZZO_FORNITORE:
                info_prezzi.append(item)
        return info_prezzi                
    
    def get_prezzo(self,codice_listino):
        info_prezzo=self.get_info_prezzo(codice_listino)
        if info_prezzo is None:
            return Decimal('0.00')
        return info_prezzo.prezzo
    
    def get_prezzo_fmt(self, codice_listino = None):
        cod_listino = int(util.get_options().LISTINO_DEFAULT_VENDITA)
        return util.euro(self.get_prezzo(cod_listino))
    
    def set_prezzo(self,codice_listino,prezzo):
        for item in self.info_prezzi:
            if item.fk_listino==codice_listino:
                item.set_prezzo(prezzo)
                return
    
    def _aggiorna_prezzi(self,listino,prezzo):
        for item in self.info_prezzi:
            if item.has_formula() and item.get_listino_formula()==listino:
                item._aggiorna_prezzo(prezzo)
    
    def set_categoria(self,obj):
        self.categoria=obj
        if obj is None:
            self.fk_categoria=None
        else:
            self.fk_categoria=obj.ID
    
    def set_sottocategoria(self,obj):
        self.sottocategoria=obj
        if obj is None:
            self.fk_sottocategoria=None
        else:
            self.fk_sottocategoria=obj.ID
    
    def set_aliquota_iva(self,aliquota_iva):
        self.aliquota_iva=aliquota_iva
        if self.aliquota_iva is None:
            self.fk_iva=None
        else:
            self.fk_iva=self.aliquota_iva.codice
            
    
ZERO=Decimal('0.00')    
#===============================================================================
# Formula
#===============================================================================
class Formula(IDObject):
    def __init__(self,listino_origine=None,ricarico=ZERO,fisso=ZERO):
        IDObject.__init__(self)
        self.fk_listino_origine=None
        self.listino_origine=listino_origine
        if listino_origine is not None:
            self.fk_listino_origine=listino_origine.ID
        #self.prezzo_origine=Decimal('0.00')
        self.ricarico=ricarico
        self.importo_fisso=fisso
    
    @reconstructor
    def on_load(self):
        self.ricarico=util.to_decimal(self.ricarico)
        self.importo_fisso=util.to_decimal(self.importo_fisso)
    
    def __str__(self):
        '''Restituisce una stringa unicode'''
        str_formula='[{0}]'.format(self.listino_origine.nome)
        if not self.ricarico.is_zero():
            str_formula+='+{0}%'.format(self.ricarico)
        if not self.importo_fisso.is_zero():
            str_formula+='+{0}'.format(util.euro(self.importo_fisso))
        return unicode(str_formula)
    
    def validate(self):
        return True
    
    def set_listino_origine(self,listino_origine):
        self.listino_origine=listino_origine
        self.fk_listino_origine=listino_origine.ID
    
    def calcola_prezzo(self,prezzo_origine):
        importo_ricarico=self._calcola_importo_ricarico(prezzo_origine)
        prezzo=prezzo_origine + importo_ricarico + self.importo_fisso
        return util.round(prezzo)
    
    def _calcola_importo_ricarico(self,prezzo_origine):
        ricarico=prezzo_origine/100*self.ricarico
        return util.round(ricarico)

    
#===============================================================================
# PrezzoProdotto
#===============================================================================
class PrezzoProdotto(object):
    def __init__(self,prodotto=None,listino=None):
        self.prodotto=prodotto#backref
        self.fk_prodotto=prodotto.ID
        self.fk_listino=listino.ID
        self.listino=listino
        self.prezzo=Decimal('0.00')
        self.fk_formula=None
        self.formula=None
    
    @reconstructor
    def on_load(self):
        self.listino=object_session(self).query(Listino).filter_by(ID=self.fk_listino).first()
        self.prezzo=util.to_decimal(self.prezzo)
    
    def validate(self):
        if not bool(self.fk_listino) or \
           not bool(self.fk_prodotto):
            raise exception.KoroInternalError("L'oggetto 'PrezzoProdotto' non è consistente!")
    
    def aggiorna_da_listino(self):
        '''Aggiorna il prezzo calcolandolo dalla formula del listino a cui è associato.
        Se il listino non ha una formula, questo metodo non fa niente.'''
        if self.listino.has_formula():
            formula=Formula(ricarico=self.listino.ricarico_formula, 
                            fisso=self.listino.importo_fisso_formula)
            prezzo_origine=self.prodotto.get_prezzo(self.listino.fk_listino_formula)
            self.set_prezzo(formula.calcola_prezzo(prezzo_origine))
            del formula
    
    def has_formula(self):
        return bool(self.fk_formula)
    
    def set_formula(self,listino_origine,ricarico,importo_fisso):
        self._check_formula(listino_origine)
        if self.formula is None:
            self.formula=Formula()
        self.formula.set_listino_origine(listino_origine)
        #self.formula.prezzo_origine=self.prodotto.get_prezzo(listino_origine)
        self.formula.ricarico=Decimal(str(ricarico))#Decimal
        self.formula.importo_fisso=Decimal(str(importo_fisso))#Decimal
        prezzo_origine=self.prodotto.get_prezzo(listino_origine.ID)
        if self.fk_listino==listino_origine.ID:
            prezzo=self.formula.calcola_prezzo(prezzo_origine)
            self.del_formula()
            self.set_prezzo(prezzo)
        else:
            self._aggiorna_prezzo(prezzo_origine)
    
    def _check_formula(self,listino_origine):                
        info_prezzo_tmp=self.prodotto.get_info_prezzo(listino_origine.ID)
        if info_prezzo_tmp.has_formula():
            listino_origine_tmp=info_prezzo_tmp.get_listino_formula()
            if self.fk_listino==listino_origine_tmp.ID:                
                raise Exception('Il listino di origine selezionato calcola il prezzo a partire dal listino corrente.\n Selezionare un altro listino.')
            else:
                self._check_formula(listino_origine_tmp)                
    
    def _aggiorna_prezzo(self,prezzo_origine):
        if self.formula is None:
            return
        self.set_prezzo(self.formula.calcola_prezzo(prezzo_origine))
    
    def formula_fmt(self):
        if self.formula is None:
            return 'Manuale'
        return str(self.formula)
    
    def get_ricarico_formula(self):
        if self.formula is None:
            return ''
        return self.formula.ricarico
    
    def get_listino_formula(self):
        if self.formula is None:
            return None
        return self.formula.listino_origine
    
    def get_importo_fisso_formula(self):
        if self.formula is None:
            return ''
        return self.formula.importo_fisso
    
    def prezzo_fmt(self):
        #self._aggiorna_prezzo()
        return util.euro(self.prezzo)
    
    def get_listino(self):
        return self.listino        
    
    def set_prezzo(self,prezzo):
        self.prezzo=Decimal(str(prezzo))
        self.prodotto._aggiorna_prezzi(self.listino,self.prezzo)    
    
    def set_prodotto(self,id_prodotto):
        if id_prodotto:
            self.fk_prodotto=long(id_prodotto)
    
    def set_listino(self,listino,session):
        self.fk_listino=listino.ID
        self.listino=listino
#        if session is not None:
#            self.listino=session.query(Listino).filter_by(codice=codice_listino).one()
        
    def is_zero(self):
        return self.prezzo.is_zero()
    
    def del_formula(self):
        if self.formula is None:
            return
        del self.formula#dovrebbe cancellarla dal database
        self.fk_formula=None
        self.formula=None
